package ru.spb.nicetu.altx.mnemo.extpoint.model.funcs;

import java.util.HashMap;
import java.util.Map;
import java.util.Map.Entry;

import ru.spb.nicetu.altx.mnemo.extpoint.model.typeConverter.IConverter;
import ru.spb.nicetu.altx.mnemo.extpoint.valuetypes.ITemplateValueType;
import ru.spb.nicetu.altx.mnemo.math.managers.ValueTypeConverterManager;
import ru.spb.nicetu.altx.mnemo.messages.RunMessage;
import ru.spb.nicetu.altx.mnemo.messages.RunMessage.RunMessageType;

/**
 * Базовый класс функций, реализующий проверку корректности типов входных
 * значений
 *
 * @author ilia
 *
 */
public abstract class AFunc extends ASimpleFunc {
  protected final static RunMessageType errorType =RunMessageType.ERROR;

  /**
   * Безопсный метод run(); Отлавливает все ошибки на уровне математических
   * функций
   *
   * @throws SimpleFuncException
   */
  final public void safeRun() throws SimpleFuncException {
    try {
      run();
    } catch (Throwable e) {
      if (!(e instanceof SimpleFuncException)) {
        RunMessage msg = new RunMessage(Messages.Error_Internal, null,
            RunMessageType.ERROR);
        notifyAllRunListeners(msg);
        throw new SimpleFuncException();
      } else {
        // TODO сделать ошибку информативной
        SimpleFuncException exc = (SimpleFuncException) e;
        throw exc;
      }
    }
  }

  /**
   * Переопределение метода run() с добавлением проверки Наследники должны
   * делать super.run()
   */

  public void run() throws SimpleFuncException {
    checkParamTypesAndCast();
  }

  /**
   * проверка значений, передаваемых в функцию на соответствие типам
   *
   * @throws SimpleFuncException
   */
  @SuppressWarnings("unchecked")
  private void checkParamTypesAndCast() throws SimpleFuncException {
    Map<String, Class> params = getParamTypes();
    Map<String, String> idsError = new HashMap<String, String>();
    for (Entry<String, Class> e : params.entrySet()) {
      String id = e.getKey();
      Class expectedClazz = e.getValue();
      ITemplateValueType<?> incomingValue = getCellValue(id);

      if (incomingValue == null) {
        notifyAllRunListeners(new RunMessage(Messages.Error_Internal, null,
            RunMessageType.ERROR));
        throw new SimpleFuncException();
      }

      Class<? extends ITemplateValueType> incomingValueClass = incomingValue.getClass();
      // копим сообщения об ошибках
//      if (false == incomingValueClass.getName().equals(
//          expectedClazz.getName())) {
      if (incomingValueClass != expectedClazz) {
        // Если классы не совпадают по именам, проверим, может ли один быть
        // скастован в другой
        if (! ValueTypeConverterManager.getInstance()
                .canCastTo(incomingValueClass, expectedClazz))
          idsError.put(id, incomingValue.getName());
        else {
          // Преобразуем значение в другой тип и записываем его в ячейку
          IConverter converter = ValueTypeConverterManager.getInstance()
              .getConverter(incomingValueClass, expectedClazz);
          Object obj = converter.convert(incomingValue, expectedClazz);
          try{
            if (expectedClazz.isInstance(obj)){
              Object convertedObj = expectedClazz.cast(obj);
              setCellValue(id, (ITemplateValueType) convertedObj);
            }else
              idsError.put(id, incomingValue.getName());
          } catch (ClassCastException e1) {
            idsError.put(id, incomingValue.getName());
          }
        }
      }
    }
    // сообщаем
    for (@SuppressWarnings("unused")
    Entry<String, String> e : idsError.entrySet()) {
      // String[] msgArgs = {"1", e.getValue()};
      notifyAllRunListeners(new RunMessage(Messages.Error_Types, null,
          RunMessageType.ERROR));
    }
    if (!idsError.isEmpty()) {
      throw new SimpleFuncException();
    }
  }

  /**
   * Отправить сообщение
   * @param message - текст сообщения
   * @param cellIDs - id ячеек, если сообщение параметрическое или null
   * @param type - тип сообщения
   * @throws SimpleFuncException если тип == ERROR, то произойдет исключение
   */
  public void send(String message, String[] cellIDs, RunMessageType type)
      throws SimpleFuncException {
    notifyAllRunListeners(new RunMessage(message, cellIDs, type));
    if (type == RunMessageType.ERROR) {
      throw new SimpleFuncException();
    }
  }

  public void __checkDoubleValueAndSendErrorIfNull(Double value, String message)
      throws SimpleFuncException {
    if ((value == null)||Double.isNaN(value)) {
      sendError(message);
    }
  }

  public void __checkValueAndSendErrorIfNull(Object object, String message)
      throws SimpleFuncException {
    if (object == null) {
      sendError(message);
    }
  }

  public void sendError(String message) throws SimpleFuncException {
    notifyAllRunListeners(new RunMessage(message, null, errorType));
    throw new SimpleFuncException();
  }


  /**
   * Метод должен вренуть карту, где ключи - id ячеек, а значения - классы
   * соответствующих данных
   *
   * @return - карта соответствия ячеек типам
   */
  @SuppressWarnings("unchecked")
  abstract protected Map<String, Class> getParamTypes();

}
